/*
 * RadioStrength.java
 *
 * Routines to turn two SPOTs into radio signal strength meters.
 *
 * author: Ron Goldman  
 * date: June 15, 2006 
 */

package org.sunspotworld.demo;

import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.sensorboard.peripheral.LEDColor;
import com.sun.spot.peripheral.radio.*;
import com.sun.spot.peripheral.Spot;
import com.sun.spot.peripheral.ILed;
import com.sun.spot.sensorboard.peripheral.ISwitch;
import com.sun.spot.sensorboard.peripheral.ILightSensor;

import com.sun.spot.io.j2me.radiogram.*;
import com.sun.spot.peripheral.TimeoutException;

import com.sun.spot.util.Utils;
import java.io.*;
import javax.microedition.io.*;

import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;

/**
 * Application to listen for radio broadcasts from another SPOT and to
 * display the radio signal strength.
 *<p>
 * The SPOT uses the LEDs to display its status as follows:
 *<p>
 * LED 0:
 *<ul>
 *<li> Red = missed an expected packet
 *<li> Green = received a packet
 *</ul>
 * LED 1-7:
 *<ul>
 *<li> Blue = signal strength meter for RSSI of received packets
 *<li> Red  = transmit power level (in binary) 0 (min) to 32 (max)
 *<li> Green = channel number (in binary) 11 (min) to 26 (max)
 *</ul>
 *<p>
 * The SPOT uses the switches to change/view the channel and power level:
 *<ul>
 *<li> SW0 = channel select
 *<li> SW1 = power select 
 *</ul>
 *
 * Note: if the channel is changed then the SPOT will not receive any OTA commands.
 * In that case just reboot the SPOT to switch back to the default radio channel.
 *
 * @author Ron Goldman
 */
public class RadioStrength extends MIDlet {
    private static final String VERSION = "1.0";
    private static final int INITIAL_CHANNEL_NUMBER = 25;  //does not change in this program
    //channel : 11-26.  must be the same as partner  Pick a different channel than any other users for this lab,
    //or take turns and turn off your SPOTs when you are not running an experiment
    private static final short PAN_ID               = IRadioPolicyManager.DEFAULT_PAN_ID;
    private static final String BROADCAST_PORT      = "42";
    private static final int RADIO_TEST_PACKET      = 42;
    private static final int PACKETS_PER_SECOND     = 5;
    private static final int PACKET_INTERVAL        = 1000 / PACKETS_PER_SECOND;
    private static final int BOOST_LED_THRESHOLD    = 600;
    private static final int MAX_BOOST_LED_THRESHOLD = 725;
    
    private ISwitch sw1 = EDemoBoard.getInstance().getSwitches()[EDemoBoard.SW1];
    private ISwitch sw2 = EDemoBoard.getInstance().getSwitches()[EDemoBoard.SW2];
    
    //for LED signal display
    private ITriColorLED leds[] = EDemoBoard.getInstance().getLEDs();
    private ITriColorLED statusLED = leds[0];
    private ITriColorLED blinkLED = null; // leds[7];
    private ILightSensor light = EDemoBoard.getInstance().getLightSensor();
    //nonstandard colors
    private LEDColor red   = new LEDColor(50,0,0);
    private LEDColor green = new LEDColor(0,20,0);
    private LEDColor blue  = new LEDColor(0,0,50);
    private LEDColor white = new LEDColor(255,255,255);
    //led coordination booleans
    private boolean ledsInUse = false;
    private boolean boostLEDs = false;
    private boolean maxBoostLEDs = false;
    
    private int channel = INITIAL_CHANNEL_NUMBER;
    private int power = 32;                             // use max transmit power for test requests

    private boolean xmitDo = true;
    private boolean recvDo = true;
    
    //
    private int repeat=100;
    
    //flash record objects
    private RecordStore rec;
    private static final String REC_KEY = "Moo!";
    private RecordEnumeration ren;
    
    /**
     * MIDlet call to start our application.
     */
    protected void startApp() throws MIDletStateChangeException {
        initialize();
        run();
    }

    /**
     * Initialize any needed variables.
     */
    private void initialize() { 
        checkLightSensor();
        statusLED.setColor(getRed()); 
        statusLED.setOn();
        IRadioPolicyManager rpm = Spot.getInstance().getRadioPolicyManager();
        rpm.setChannelNumber(channel);
        rpm.setPanId(PAN_ID);
        rpm.setOutputPower(power - 32);  //power goes from -32 to 0.  Even though setOutputPower takes numbers up to 32, powers higher than 0 work the same as 0
        
        //display old data and setup recordstore
        printData();
        try {
            rec = RecordStore.openRecordStore(REC_KEY, true);
        } catch (RecordStoreException ex) {
            ex.printStackTrace();
        }
    }
    
    private void printData() {
        try {
            rec = RecordStore.openRecordStore(REC_KEY, false);
            ren = rec.enumerateRecords(new CowRecordFilter(), null, false);
            System.out.println(CowRecord.getHeader());
            while (ren.hasNextElement()) {
                System.out.println(new CowRecord(ren.nextRecord()));
            }
            if (sw1.isClosed() && sw2.isClosed()) {  //hold down both switches to erase old data
                rec.closeRecordStore();
                RecordStore.deleteRecordStore(REC_KEY);
            }
        } catch (RecordStoreException ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Main application run loop.
     */
    private void run() {
        System.out.println("Radio Signal Strength Test (version " + VERSION + ")");
        System.out.println("Packet interval = " + PACKET_INTERVAL + " msec");
        
        
        respondToSwitches();            // this thread will handle User input via switches
    }

    /**
     * Loop waiting for user to press a switch.
     *<p>
     * Since ISwitch.waitForChange() doesn't really block we can loop on both switches ourself.
     *<p>
     * Detect when either switch is pressed by displaying the current value.
     * After 1 second, if it is still pressed start cycling through values every 0.5 seconds.
     * After cycling through 4 new values speed up the cycle time to every 0.3 seconds.
     * When cycle reaches the max value minus one revert to slower cycle speed.
     * Ignore other switch transitions for now.
     *
     */
    private void respondToSwitches() {
        while (true) {
            Utils.sleep(100);         // check every 0.1 seconds
            int cnt = 0;
            if (sw1.isClosed()) {
                ledsInUse = true;
                displayNumber(repeat/10, getGreen());
                Utils.sleep(1000);    // wait 1.0 second
                if (sw2.isClosed()) {  //hold down both switches to signal a new experiment
                    signalExperiment();
                }
                else if (sw1.isClosed()) {  //hold down just sw1 to change the experiment sample size
                    while (sw1.isClosed()) {
                        repeat += 10;
                        if (repeat > 130) { cnt = 0; }
                        if (repeat > 150) { repeat = 10; }
                        displayNumber(repeat/10, getGreen());
                        cnt++;
                        Utils.sleep(cnt < 5 ? 500 : 300);    // wait 0.5 second
                    }
                }
                Utils.sleep(1000);    // wait 1.0 second
                //System.out.println(repeat);
                displayNumber(0, blue);
            }
            if (sw2.isClosed()) {
                cnt = 0;
                ledsInUse = true;
                displayNumber(power, getRed());
                Utils.sleep(1000);    // wait 1.0 second
                if (sw1.isClosed()) {  //hold down both switches to signal a new experiment
                    signalExperiment();
                }
                else if (sw2.isClosed()) {
                    while (sw2.isClosed()) {  //hold down just sw2 to change the experiment transmit power
                        power++;
                        if (power > 30) { cnt = 0; }
                        if (power > 32) { power = 0; }
                        displayNumber(power, getRed());
                        cnt++;
                        Utils.sleep(cnt < 5 ? 500 : 300);    // wait 0.5 second
                    }
                }
                Utils.sleep(1000);    // wait 1.0 second
                displayNumber(0, blue);
            }
            ledsInUse = false;
            checkLightSensor();
        }
    }

    /**
     * signal partner to start a new test
     */
    private void signalExperiment () {
        ILed led = Spot.getInstance().getGreenLed();
        RadiogramConnection txConn = null;
        xmitDo = true;
        while (xmitDo) {
            try {
                txConn = (RadiogramConnection)Connector.open("radiogram://broadcast:" + BROADCAST_PORT);
                txConn.setMaxBroadcastHops(0);      // don't want packets being rebroadcasted
                Datagram xdg = txConn.newDatagram(txConn.getMaximumLength());
                long count = 0;
                boolean ledOn = false;
                led.setOn();
                if (!ledsInUse && blinkLED != null) {
                    ledOn = blinkLED.isOn();
                    blinkLED.setRGB(blinkLED.getRed(), boostLEDs ? 255 : 20, 0);
                    blinkLED.setOn();
                }
                long nextTime = System.currentTimeMillis() + PACKET_INTERVAL;
                count++;
                if (count >= Long.MAX_VALUE) { count = 0; }
                xdg.reset();
                xdg.writeByte(RADIO_TEST_PACKET);
                xdg.writeInt(power);
                xdg.writeInt(repeat);
                xdg.writeLong(count);
                txConn.send(xdg);
                led.setOff();
                System.out.println("Instructions Sent");
                if (!ledsInUse && blinkLED != null) {
                    blinkLED.setRGB(blinkLED.getRed(), 0, 0);
                    if (!ledOn) {
                        blinkLED.setOff();                            
                    }
                }
                xmitDo=false;
            } catch (IOException ex) {
                // ignore
            } finally {
                if (txConn != null) {
                    try {
                        txConn.close();
                    } catch (IOException ex) { }
                }
            }
        }
        recordExperiment();
    }
    
    /**
     * Loop to receive packets and display their RSSI level in the LEDs
     * Make and store to flash a CowRecord of this experiment
     */
    private void recordExperiment () {
        long stop = System.currentTimeMillis() + repeat * (PACKET_INTERVAL + 5);
        int count = 0;
        ILed led = Spot.getInstance().getRedLed();
        RadiogramConnection rcvConn = null;
        recvDo = true;
        int maxQ = -100;
        int minQ = 100;
        int q = 0;
        double avgRSSI = 0;
        double avgCCI = 0;
        int nothing = 0;
        while (count < repeat && System.currentTimeMillis() < stop) {
            try {
                rcvConn = (RadiogramConnection) Connector.open("radiogram://:" + BROADCAST_PORT);
                rcvConn.setTimeout(PACKET_INTERVAL - 5);
                Radiogram rdg = (Radiogram) rcvConn.newDatagram(rcvConn.getMaximumLength());
                boolean ledOn = false;
                while (count < repeat && System.currentTimeMillis() < stop) {
                    try {
                        rdg.reset();
                        rcvConn.receive(rdg); 
                        byte packetType = rdg.readByte();
                        if (packetType == RADIO_TEST_PACKET) {
                            led.setOn();
                            if (!ledsInUse && blinkLED != null) {
                                ledOn = blinkLED.isOn();
                                blinkLED.setRGB(boostLEDs ? 255 : 50, blinkLED.getGreen(), 0);
                                blinkLED.setOn();
                            }
                            statusLED.setColor(getGreen());
                            statusLED.setOn();
                            int pow = rdg.readInt();
                            q = rdg.getRssi();
                            avgRSSI = avgRSSI*((double)count/(count+1)) + (double)q/(count+1);
                            System.out.println(avgRSSI);
                            avgCCI = avgCCI*((double)count/(count+1)) + rdg.getCorr()/(count+1);
                            System.out.println(avgCCI);
                            count++;
                            //if (!ledsInUse) {
                                displayLevel(q, 40, -50, getBlue());
                            //}
                            if (q > maxQ) {
                                maxQ = q;
                                System.out.println("Max = " + maxQ + " power = " + pow);
                            }
                            if (q < minQ) {
                                minQ = q;
                                System.out.println("Min = " + minQ + " power = " + pow);
                            }
                            nothing = 0;
                            if (!ledsInUse && blinkLED != null) {
                                blinkLED.setRGB(0, blinkLED.getGreen(), 0);
                                if (!ledOn) {
                                    blinkLED.setOff();
                                }
                            }
                            led.setOff();
                        }
                    } catch (TimeoutException tex) {
                        // timeout - display no packet received
                        statusLED.setColor(getRed());
                        statusLED.setOn();
                        nothing++;
                        if (nothing > 2 * PACKETS_PER_SECOND && !ledsInUse) {
                            displayLevel(-50, 40, -50, getBlue()); 
                        }
                    }
                }
            } catch (IOException ex) {
                // ignore
            } finally {
                if (rcvConn != null) {
                    try {
                        rcvConn.close();
                    } catch (IOException ex) {
                    }
                }
            }
        }
        try {
            //System.out.println(new CowRecord(count, repeat, avgRSSI, avgCCI));
            rec.addRecord(new CowRecord(count, repeat, avgRSSI, avgCCI).toByteArray(), 0, CowRecord.RECORD_SIZE);
        } catch (RecordStoreNotOpenException ex) {
            ex.printStackTrace();
        } catch (RecordStoreException ex) {
            ex.printStackTrace();
        } 
        printData();
    }
    
    /**
     * This will never be called by the Squawk VM.
     */
    protected void pauseApp() {
        // This will never be called by the Squawk VM
    }

    /**
     * Called if the MIDlet is terminated by the system.
     * I.e. if startApp throws any exception other than MIDletStateChangeException,
     * if the isolate running the MIDlet is killed with Isolate.exit(), or
     * if VM.stopVM() is called.
     * 
     * It is not called if MIDlet.notifyDestroyed() was called.
     *
     * @param unconditional If true when this method is called, the MIDlet must
     *    cleanup and release all resources. If false the MIDlet may throw
     *    MIDletStateChangeException  to indicate it does not want to be destroyed
     *    at this time.
     */
    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {
    }
    
    //LED Display Methods
    
    /**
     * Return bright or dim red.
     *
     * @returns appropriately bright red LED settings
     */
    private LEDColor getRed() {
        return boostLEDs ? LEDColor.RED : red;
    }
    
    /**
     * Return bright or dim green.
     *
     * @returns appropriately bright green LED settings
     */
    private LEDColor getGreen() {
        return boostLEDs ? LEDColor.GREEN : green;
    }

    /**
     * Return bright or dim blue.
     *
     * @returns appropriately bright blue LED settings
     */
    private LEDColor getBlue() {
        return maxBoostLEDs ? white : boostLEDs ? LEDColor.BLUE : blue;
    }

    /**
     * Check if in really bright environment.
     *
     * @returns true if it's really bright, false if not so bright
     */
    private void checkLightSensor() {
        try {
            int val = light.getValue();
            boostLEDs = (val > BOOST_LED_THRESHOLD);
            maxBoostLEDs = (val > MAX_BOOST_LED_THRESHOLD);
        } catch (IOException ex) { }
    }

    /**
     * Display a number (base 2) in LEDs 1-7
     *
     * @param val the number to display
     * @param col the color to display in LEDs
     */
    private void displayNumber(int val, LEDColor col) {
        for (int i = 0, mask = 1; i < 7; i++, mask <<= 1) {
            leds[7-i].setColor(col);
            leds[7-i].setOn((val & mask) != 0);
        }
    }
    
    /**
     * Auxiliary routine to scale the brightness of the LED so it is more in 
     * keeping with how people perceive brightness.
     *
     * @param x the raw value to display
     * @param col the maximum LED brightness to use
     * @param perLed the maximum value to display
     * @returns the scaled brightness to actually display
     */
    private int lightValue (int x, int col, int perLed){
        if (x <= 0 || col <= 0) return 0;
        if (x >= perLed) return col;
        return  ( x * x * x * col ) /(perLed * perLed * perLed);
    }

    /**
     * Display a vU like level in LEDs 1-7
     *
     * @param val the level to display
     * @param max the maximum value expected
     * @param min the minimum value expected
     * @param col the color to display in LEDs
     */
    private void displayLevel(int val, int max, int min, LEDColor col) {
        int LEDS_TO_USE = 7;
        int MAX_LED = 7;
        int range = max - min + 1;
        int perLed = range / LEDS_TO_USE;
        int bucket = (val - min + 1) / perLed;
        int part = (val - min + 1) - bucket * perLed;
        for (int i = 0; i < LEDS_TO_USE; i++) {
            if (bucket > i) {
                leds[MAX_LED-i].setColor(col);
                leds[MAX_LED-i].setOn();
            } else if (bucket == i) {
                leds[MAX_LED-i].setRGB(lightValue(part, col.red(), perLed), lightValue(part, col.green(), perLed), lightValue(part, col.blue(), perLed));
                leds[MAX_LED-i].setOn();
            } else {
                leds[MAX_LED-i].setOff();
            }
        }
    }
}
